package main

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
	"gollect/monitor/db"
)

// DeploymentConfig holds configuration for trode deployment
type DeploymentConfig struct {
	SSHHost     string
	SSHPort     int
	SSHUser     string
	SSHKeyPath  string
	SSHPassword string
	RemoteDir   string
	TrodePort   int
	Config      *TrodeConfig
}

// TrodeConfig represents the configuration for a deployed trode
type TrodeConfig struct {
	CPUInterval     int  `yaml:"cpu_interval"`
	MemoryInterval  int  `yaml:"memory_interval"`
	DiskInterval    int  `yaml:"disk_interval"`
	IOInterval      int  `yaml:"io_interval"`
	ProcessInterval int  `yaml:"process_interval"`
	UserInterval    int  `yaml:"user_interval"`
	Port            int  `yaml:"port"`
	Verbose         bool `yaml:"verbose"`
}

// DeploymentResult represents the result of the deployment process
type DeploymentResult struct {
	Success     bool   `json:"success"`
	Message     string `json:"message"`
	Error       string `json:"error,omitempty"`
	Step        string `json:"step,omitempty"`
	TrodePort   int    `json:"trode_port,omitempty"`
	ServiceName string `json:"service_name,omitempty"`
}

// Deployer handles trode deployment to remote nodes
type Deployer struct {
	config *DeploymentConfig
	client *ssh.Client
}

// NewDeployer creates a new deployer instance
func NewDeployer(config *DeploymentConfig) (*Deployer, error) {
	// Setup SSH authentication
	var authMethod ssh.AuthMethod

	if config.SSHKeyPath != "" {
		key, err := os.ReadFile(config.SSHKeyPath)
		if err != nil {
			return nil, fmt.Errorf("failed to read SSH key: %w", err)
		}

		signer, err := ssh.ParsePrivateKey(key)
		if err != nil {
			return nil, fmt.Errorf("failed to parse SSH key: %w", err)
		}

		authMethod = ssh.PublicKeys(signer)
	} else if config.SSHPassword != "" {
		authMethod = ssh.Password(config.SSHPassword)
	} else {
		return nil, fmt.Errorf("either SSH key path or password must be provided")
	}

	// Create SSH client config
	sshConfig := &ssh.ClientConfig{
		User:            config.SSHUser,
		Auth:            []ssh.AuthMethod{authMethod},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         30 * time.Second,
	}

	// Connect to remote host
	client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", config.SSHHost, config.SSHPort), sshConfig)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to %s:%d: %w", config.SSHHost, config.SSHPort, err)
	}

	return &Deployer{
		config: config,
		client: client,
	}, nil
}

// DeployTrode deploys a trode to the remote node
func (d *Deployer) DeployTrode(ctx context.Context) (*DeploymentResult, error) {
	result := &DeploymentResult{}

	LogInfo("Deploying trode to %s:%d", d.config.SSHHost, d.config.SSHPort)

	// Step 1: Create remote directory
	if err := d.createRemoteDirectory(); err != nil {
		result.Error = fmt.Sprintf("Failed to create remote directory: %v", err)
		result.Step = "directory_creation"
		return result, nil
	}

	// Step 2: Copy trode binary
	if err := d.copyTrodeBinary(); err != nil {
		result.Error = fmt.Sprintf("Failed to copy trode binary: %v", err)
		result.Step = "binary_copy"
		return result, nil
	}

	// Step 3: Generate and copy configuration
	if err := d.generateAndCopyConfig(); err != nil {
		result.Error = fmt.Sprintf("Failed to generate and copy config: %v", err)
		result.Step = "config_generation"
		return result, nil
	}

	// Step 4: Create systemd service
	if err := d.createSystemdService(); err != nil {
		result.Error = fmt.Sprintf("Failed to create systemd service: %v", err)
		result.Step = "service_creation"
		return result, nil
	}

	// Step 5: Start the trode
	if err := d.startTrode(); err != nil {
		result.Error = fmt.Sprintf("Failed to start trode: %v", err)
		result.Step = "service_start"
		return result, nil
	}

	// Step 6: Verify trode is running and receiving data
	if err := d.verifyTrodeOperation(); err != nil {
		result.Error = fmt.Sprintf("Failed to verify trode operation: %v", err)
		result.Step = "verification"
		return result, nil
	}

	result.Success = true
	result.Message = "Trode deployed and verified successfully"
	result.TrodePort = d.config.TrodePort
	result.ServiceName = fmt.Sprintf("holter-trode-%d", d.config.TrodePort)

	LogInfo("Successfully deployed trode to %s:%d", d.config.SSHHost, d.config.SSHPort)
	return result, nil
}

// createRemoteDirectory creates the remote directory for the trode
func (d *Deployer) createRemoteDirectory() error {
	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	// Create the holter directory structure
	cmd := fmt.Sprintf("sudo mkdir -p %s && sudo chown %s:%s %s && sudo chmod 755 %s",
		d.config.RemoteDir, d.config.SSHUser, d.config.SSHUser, d.config.RemoteDir, d.config.RemoteDir)
	
	LogInfo("Creating remote directory: %s", d.config.RemoteDir)
	return session.Run(cmd)
}

// copyTrodeBinary copies the trode binary to the remote node
func (d *Deployer) copyTrodeBinary() error {
	// Find the trode binary in the local trode directory
	localTrodePath := "trode/trode"
	if _, err := os.Stat(localTrodePath); os.IsNotExist(err) {
		// Try alternative paths
		alternativePaths := []string{
			"trode/gollect-trode",
			"../trode/trode",
			"../trode/gollect-trode",
		}
		
		for _, path := range alternativePaths {
			if _, err := os.Stat(path); err == nil {
				localTrodePath = path
				break
			}
		}
		
		if _, err := os.Stat(localTrodePath); os.IsNotExist(err) {
			return fmt.Errorf("trode binary not found. Please build it first: cd trode && go build -o trode .")
		}
	}

	LogInfo("Copying trode binary from: %s", localTrodePath)

	// Open local file
	localFile, err := os.Open(localTrodePath)
	if err != nil {
		return fmt.Errorf("failed to open local trode binary: %w", err)
	}
	defer localFile.Close()

	// Create remote file
	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	// Use scp-like functionality
	go func() {
		defer session.Close()
		w, _ := session.StdinPipe()
		defer w.Close()
		fmt.Fprintf(w, "C0755 %d trode\n", 0)
		io.Copy(w, localFile)
		fmt.Fprint(w, "\x00")
	}()

	return session.Run(fmt.Sprintf("scp -t %s", d.config.RemoteDir))
}

// generateAndCopyConfig generates and copies the trode configuration
func (d *Deployer) generateAndCopyConfig() error {
	config := d.config.Config
	if config == nil {
		config = &TrodeConfig{
			CPUInterval:     5,
			MemoryInterval:  5,
			DiskInterval:    10,
			IOInterval:      10,
			ProcessInterval: 30,
			UserInterval:    30,
			Port:            d.config.TrodePort,
			Verbose:         false,
		}
	}

	// Generate config content
	configContent := fmt.Sprintf(`cpu_interval: %d
memory_interval: %d
disk_interval: %d
io_interval: %d
process_interval: %d
user_interval: %d
port: %d
verbose: %t
`, config.CPUInterval, config.MemoryInterval, config.DiskInterval,
		config.IOInterval, config.ProcessInterval, config.UserInterval,
		config.Port, config.Verbose)

	// Create temporary local config file
	tempConfigPath := fmt.Sprintf("/tmp/holter-config-%d.yaml", d.config.TrodePort)
	if err := os.WriteFile(tempConfigPath, []byte(configContent), 0644); err != nil {
		return fmt.Errorf("failed to create temporary config file: %w", err)
	}
	defer os.Remove(tempConfigPath)

	LogInfo("Copying configuration file")

	// Copy config file to remote
	localFile, err := os.Open(tempConfigPath)
	if err != nil {
		return err
	}
	defer localFile.Close()

	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	go func() {
		defer session.Close()
		w, _ := session.StdinPipe()
		defer w.Close()
		fmt.Fprintf(w, "C0644 %d config.yaml\n", len(configContent))
		io.Copy(w, localFile)
		fmt.Fprint(w, "\x00")
	}()

	return session.Run(fmt.Sprintf("scp -t %s", d.config.RemoteDir))
}

// createSystemdService creates a systemd service for the trode
func (d *Deployer) createSystemdService() error {
	serviceName := fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
	
	serviceContent := fmt.Sprintf(`[Unit]
Description=Holter Trode Monitoring Agent
After=network.target
Wants=network.target

[Service]
Type=simple
User=%s
Group=%s
WorkingDirectory=%s
ExecStart=%s/trode -config %s/config.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=%s

# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=%s

[Install]
WantedBy=multi-user.target
`, d.config.SSHUser, d.config.SSHUser, d.config.RemoteDir, d.config.RemoteDir, d.config.RemoteDir, serviceName, d.config.RemoteDir)

	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	// Write service file
	cmd := fmt.Sprintf("echo '%s' | sudo tee /etc/systemd/system/%s.service",
		strings.ReplaceAll(serviceContent, "'", "'\"'\"'"), serviceName)

	LogInfo("Creating systemd service: %s", serviceName)
	if err := session.Run(cmd); err != nil {
		return err
	}

	// Reload systemd and enable service
	session, err = d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	return session.Run("sudo systemctl daemon-reload")
}

// startTrode starts the trode process
func (d *Deployer) startTrode() error {
	serviceName := fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
	
	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	// Make trode binary executable
	chmodCmd := fmt.Sprintf("chmod +x %s/trode", d.config.RemoteDir)
	if err := session.Run(chmodCmd); err != nil {
		return fmt.Errorf("failed to make trode executable: %w", err)
	}

	// Enable and start the service
	LogInfo("Starting trode service: %s", serviceName)
	
	// Enable the service
	session, err = d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	enableCmd := fmt.Sprintf("sudo systemctl enable %s", serviceName)
	if err := session.Run(enableCmd); err != nil {
		return fmt.Errorf("failed to enable service: %w", err)
	}

	// Start the service
	session, err = d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	startCmd := fmt.Sprintf("sudo systemctl start %s", serviceName)
	if err := session.Run(startCmd); err != nil {
		return fmt.Errorf("failed to start service: %w", err)
	}

	LogInfo("Trode service started successfully")
	return nil
}

// verifyTrodeOperation verifies that the trode is running and receiving data
func (d *Deployer) verifyTrodeOperation() error {
	serviceName := fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
	
	// Wait a bit for the service to start
	time.Sleep(3 * time.Second)

	// Check if service is active
	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	checkCmd := fmt.Sprintf("sudo systemctl is-active %s", serviceName)
	output, err := session.Output(checkCmd)
	if err != nil {
		return fmt.Errorf("service is not active: %w", err)
	}

	if strings.TrimSpace(string(output)) != "active" {
		return fmt.Errorf("service is not active, status: %s", string(output))
	}

	// Check if trode is listening on the port
	session, err = d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	portCheckCmd := fmt.Sprintf("sudo netstat -tlnp | grep :%d", d.config.TrodePort)
	output, err = session.Output(portCheckCmd)
	if err != nil {
		return fmt.Errorf("trode is not listening on port %d: %w", d.config.TrodePort, err)
	}

	// Try to connect to the trode gRPC service
	if err := d.testTrodeConnection(); err != nil {
		return fmt.Errorf("failed to connect to trode gRPC service: %w", err)
	}

	LogInfo("Trode verification successful - service is running and listening on port %d", d.config.TrodePort)
	return nil
}

// testTrodeConnection tests the connection to the trode gRPC service
func (d *Deployer) testTrodeConnection() error {
	// This is a simple test - in a real implementation, you would use the gRPC client
	// to actually connect and request data from the trode
	
	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	// Test if the port is reachable
	testCmd := fmt.Sprintf("timeout 5 bash -c '</dev/tcp/%s/%d'", d.config.SSHHost, d.config.TrodePort)
	if err := session.Run(testCmd); err != nil {
		return fmt.Errorf("port %d is not reachable: %w", d.config.TrodePort, err)
	}

	LogInfo("Trode gRPC service is reachable on port %d", d.config.TrodePort)
	return nil
}

// StopTrode stops the trode process
func (d *Deployer) StopTrode() error {
	serviceName := fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
	
	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	stopCmd := fmt.Sprintf("sudo systemctl stop %s", serviceName)
	LogInfo("Stopping trode service: %s", serviceName)
	
	if err := session.Run(stopCmd); err != nil {
		return fmt.Errorf("failed to stop service: %w", err)
	}

	LogInfo("Trode service stopped successfully")
	return nil
}

// CheckTrodeStatus checks if the trode is running
func (d *Deployer) CheckTrodeStatus() (bool, error) {
	serviceName := fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
	
	session, err := d.client.NewSession()
	if err != nil {
		return false, err
	}
	defer session.Close()

	checkCmd := fmt.Sprintf("sudo systemctl is-active %s", serviceName)
	output, err := session.Output(checkCmd)
	if err != nil {
		return false, err
	}

	return strings.TrimSpace(string(output)) == "active", nil
}

// GetTrodeAddress returns the trode's gRPC address
func (d *Deployer) GetTrodeAddress() string {
	return fmt.Sprintf("%s:%d", d.config.SSHHost, d.config.TrodePort)
}

// GetTrodeServiceName returns the systemd service name
func (d *Deployer) GetTrodeServiceName() string {
	return fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
}

// GetTrodeLogs retrieves the trode service logs
func (d *Deployer) GetTrodeLogs(lines int) (string, error) {
	serviceName := fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
	
	session, err := d.client.NewSession()
	if err != nil {
		return "", err
	}
	defer session.Close()

	logCmd := fmt.Sprintf("sudo journalctl -u %s -n %d --no-pager", serviceName, lines)
	output, err := session.Output(logCmd)
	if err != nil {
		return "", fmt.Errorf("failed to get logs: %w", err)
	}

	return string(output), nil
}

// RestartTrode restarts the trode service
func (d *Deployer) RestartTrode() error {
	serviceName := fmt.Sprintf("holter-trode-%d", d.config.TrodePort)
	
	session, err := d.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	restartCmd := fmt.Sprintf("sudo systemctl restart %s", serviceName)
	LogInfo("Restarting trode service: %s", serviceName)
	
	if err := session.Run(restartCmd); err != nil {
		return fmt.Errorf("failed to restart service: %w", err)
	}

	LogInfo("Trode service restarted successfully")
	return nil
}

// Close closes the SSH connection
func (d *Deployer) Close() error {
	if d.client != nil {
		return d.client.Close()
	}
	return nil
}

// DeployNodeWithBootstrap deploys a trode to a node with bootstrap support
func DeployNodeWithBootstrap(node *db.Node, trodePort int) (*DeploymentResult, error) {
	LogInfo("Deploying trode to node: %s (%s) on port %d", node.Name, node.Address, trodePort)

	// Check if node is bootstrapped
	if !node.Bootstraped {
		LogInfo("Node %s is not bootstrapped, attempting bootstrap first", node.Name)
		
		// Create bootstrap config
		bootstrapConfig := &BootstrapConfig{
			SSHHost:     node.Address,
			SSHPort:     node.SSHPort,
			SSHUser:     node.SSHUser,
			SSHPassword: node.SSHPassword,
			RemoteDir:   node.RemoteDir,
			KeyName:     "gollect-key",
		}

		// Create bootstrapper and run bootstrap
		bootstrapper := NewBootstrapper(bootstrapConfig)
		bootstrapResult, err := bootstrapper.Bootstrap(context.Background())
		if err != nil {
			return &DeploymentResult{
				Success: false,
				Error:   fmt.Sprintf("Bootstrap failed: %v", err),
				Step:    "bootstrap_error",
			}, nil
		}

		if !bootstrapResult.Success {
			return &DeploymentResult{
				Success: false,
				Error:   fmt.Sprintf("Bootstrap failed: %s", bootstrapResult.Error),
				Step:    bootstrapResult.Step,
			}, nil
		}

		// Update node with bootstrap information
		node.SSHKeyPath = bootstrapResult.KeyPath
		node.Bootstraped = true
		node.SSHPassword = "" // Clear password after successful bootstrap

		// Update database
		if err := database.UpdateNodeBootstrapStatus(node.ID, true, bootstrapResult.KeyPath); err != nil {
			LogError("Failed to update node bootstrap status: %v", err)
		}

		LogInfo("Bootstrap completed successfully for node %s", node.Name)
	}

	// Create deployment configuration
	deployConfig := &DeploymentConfig{
		SSHHost:     node.Address,
		SSHPort:     node.SSHPort,
		SSHUser:     node.SSHUser,
		SSHKeyPath:  node.SSHKeyPath,
		SSHPassword: node.SSHPassword,
		RemoteDir:   node.RemoteDir,
		TrodePort:   trodePort,
		Config: &TrodeConfig{
			CPUInterval:     5,
			MemoryInterval:  5,
			DiskInterval:    10,
			IOInterval:      10,
			ProcessInterval: 30,
			UserInterval:    30,
			Port:            trodePort,
			Verbose:         false,
		},
	}

	deployer, err := NewDeployer(deployConfig)
	if err != nil {
		return &DeploymentResult{
			Success: false,
			Error:   fmt.Sprintf("Failed to create deployer: %v", err),
			Step:    "deployer_creation",
		}, nil
	}
	defer deployer.Close()

	ctx := context.Background()
	result, err := deployer.DeployTrode(ctx)
	if err != nil {
		return &DeploymentResult{
			Success: false,
			Error:   fmt.Sprintf("Deployment failed: %v", err),
			Step:    "trode_deployment",
		}, nil
	}

	if result.Success {
		LogInfo("Complete deployment successful for node %s", node.Name)
		result.Message = fmt.Sprintf("Node %s deployed successfully with trode on port %d", node.Name, trodePort)
	} else {
		LogError("Deployment failed for node %s: %s", node.Name, result.Error)
	}

	return result, nil
}

// DeployTrodeToNode deploys a trode to a specific node
func DeployTrodeToNode(nodeID int, trodePort int) (*DeploymentResult, error) {
	// Get node from database
	node, err := database.GetNode(nodeID)
	if err != nil {
		return &DeploymentResult{
			Success: false,
			Error:   fmt.Sprintf("Failed to get node: %v", err),
			Step:    "node_retrieval",
		}, nil
	}

	return DeployNodeWithBootstrap(node, trodePort)
}

// DeployTrodeToAllNodes deploys trodes to all nodes in the database
func DeployTrodeToAllNodes(startPort int) ([]*DeploymentResult, error) {
	nodes, err := database.GetAllNodes()
	if err != nil {
		return nil, fmt.Errorf("failed to get nodes: %w", err)
	}

	var results []*DeploymentResult
	currentPort := startPort

	for _, node := range nodes {
		LogInfo("Deploying to node: %s (%s)", node.Name, node.Address)
		
		result, err := DeployNodeWithBootstrap(node, currentPort)
		if err != nil {
			LogError("Failed to deploy to node %s: %v", node.Name, err)
			result = &DeploymentResult{
				Success: false,
				Error:   fmt.Sprintf("Deployment error: %v", err),
				Step:    "deployment_error",
			}
		}

		results = append(results, result)
		currentPort++
	}

	return results, nil
}

// StopTrodeOnNode stops the trode on a specific node
func StopTrodeOnNode(nodeID int, trodePort int) error {
	node, err := database.GetNode(nodeID)
	if err != nil {
		return fmt.Errorf("failed to get node: %w", err)
	}

	deployConfig := &DeploymentConfig{
		SSHHost:     node.Address,
		SSHPort:     node.SSHPort,
		SSHUser:     node.SSHUser,
		SSHKeyPath:  node.SSHKeyPath,
		SSHPassword: node.SSHPassword,
		RemoteDir:   node.RemoteDir,
		TrodePort:   trodePort,
	}

	deployer, err := NewDeployer(deployConfig)
	if err != nil {
		return fmt.Errorf("failed to create deployer: %w", err)
	}
	defer deployer.Close()

	return deployer.StopTrode()
}

// CheckTrodeStatusOnNode checks if the trode is running on a specific node
func CheckTrodeStatusOnNode(nodeID int, trodePort int) (bool, error) {
	node, err := database.GetNode(nodeID)
	if err != nil {
		return false, fmt.Errorf("failed to get node: %w", err)
	}

	deployConfig := &DeploymentConfig{
		SSHHost:     node.Address,
		SSHPort:     node.SSHPort,
		SSHUser:     node.SSHUser,
		SSHKeyPath:  node.SSHKeyPath,
		SSHPassword: node.SSHPassword,
		RemoteDir:   node.RemoteDir,
		TrodePort:   trodePort,
	}

	deployer, err := NewDeployer(deployConfig)
	if err != nil {
		return false, fmt.Errorf("failed to create deployer: %w", err)
	}
	defer deployer.Close()

	return deployer.CheckTrodeStatus()
}

// GetTrodeLogsFromNode retrieves logs from the trode on a specific node
func GetTrodeLogsFromNode(nodeID int, trodePort int, lines int) (string, error) {
	node, err := database.GetNode(nodeID)
	if err != nil {
		return "", fmt.Errorf("failed to get node: %w", err)
	}

	deployConfig := &DeploymentConfig{
		SSHHost:     node.Address,
		SSHPort:     node.SSHPort,
		SSHUser:     node.SSHUser,
		SSHKeyPath:  node.SSHKeyPath,
		SSHPassword: node.SSHPassword,
		RemoteDir:   node.RemoteDir,
		TrodePort:   trodePort,
	}

	deployer, err := NewDeployer(deployConfig)
	if err != nil {
		return "", fmt.Errorf("failed to create deployer: %w", err)
	}
	defer deployer.Close()

	return deployer.GetTrodeLogs(lines)
}

// RestartTrodeOnNode restarts the trode on a specific node
func RestartTrodeOnNode(nodeID int, trodePort int) error {
	node, err := database.GetNode(nodeID)
	if err != nil {
		return fmt.Errorf("failed to get node: %w", err)
	}

	deployConfig := &DeploymentConfig{
		SSHHost:     node.Address,
		SSHPort:     node.SSHPort,
		SSHUser:     node.SSHUser,
		SSHKeyPath:  node.SSHKeyPath,
		SSHPassword: node.SSHPassword,
		RemoteDir:   node.RemoteDir,
		TrodePort:   trodePort,
	}

	deployer, err := NewDeployer(deployConfig)
	if err != nil {
		return fmt.Errorf("failed to create deployer: %w", err)
	}
	defer deployer.Close()

	return deployer.RestartTrode()
}
